package com.wg.code.core.builder;

import cn.hutool.core.util.RuntimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.TimeUnit;

/**
 * 构建 Vue 项目 (适用于 Linux/Unix-like 环境)
 *
 * @author wg
 * @date 2024-05-22
 */
@Slf4j
@Component
public class VueProjectBuilder {

    /**
     * 异步构建 Vue 项目
     *
     * @param projectPath Vue 项目的根目录路径
     */
    public void buildProjectAsync(String projectPath) {
        // 使用虚拟线程以支持高并发的异步任务，这是 JDK 21+ 的特性
        Thread.ofVirtual().name("vue-builder-" + System.currentTimeMillis())
                .start(() -> {
                    try {
                        buildProject(projectPath);
                    } catch (Exception e) {
                        log.error("异步构建 Vue 项目时发生异常: {}", e.getMessage(), e);
                    }
                });
    }

    /**
     * 同步构建 Vue 项目
     *
     * @param projectPath 项目根目录路径
     * @return 是否构建成功
     */
    public boolean buildProject(String projectPath) {
        File projectDir = new File(projectPath);
        if (!projectDir.exists() || !projectDir.isDirectory()) {
            log.error("项目目录不存在：{}", projectPath);
            return false;
        }

        // 检查是否有 package.json 文件
        File packageJsonFile = new File(projectDir, "package.json");
        if (!packageJsonFile.exists()) {
            log.error("项目目录中没有 package.json 文件：{}", projectPath);
            return false;
        }

        log.info("开始构建 Vue 项目：{}", projectPath);

        // 步骤 1: 执行 npm install
        if (!executeNpmInstall(projectDir)) {
            log.error("npm install 执行失败：{}", projectPath);
            return false;
        }

        // 步骤 2: 执行 npm run build
        if (!executeNpmBuild(projectDir)) {
            log.error("npm run build 执行失败：{}", projectPath);
            return false;
        }

        // 步骤 3: 验证构建产物 dist 目录是否生成
        File distDir = new File(projectDir, "dist");
        if (!distDir.exists() || !distDir.isDirectory()) {
            log.error("构建完成但 dist 目录未生成：{}", projectPath);
            return false;
        }

        log.info("Vue 项目构建成功，产物目录：{}/dist", projectPath);
        return true;
    }

    /**
     * 执行 npm install 命令
     */
    private boolean executeNpmInstall(File projectDir) {
        log.info("正在执行 npm install...");
        // 在 Linux 中，命令就是 "npm"
        String command = "npm install";
        return executeCommand(projectDir, command, 300); // 5分钟超时
    }

    /**
     * 执行 npm run build 命令
     */
    private boolean executeNpmBuild(File projectDir) {
        log.info("正在执行 npm run build...");
        // 在 Linux 中，命令就是 "npm run build"
        String command = "npm run build";
        return executeCommand(projectDir, command, 180); // 3分钟超时
    }

    /**
     * 执行外部命令
     *
     * @param workingDir     命令执行的工作目录
     * @param command        要执行的命令字符串
     * @param timeoutSeconds 超时时间（秒）
     * @return 命令是否执行成功
     */
    private boolean executeCommand(File workingDir, String command, int timeoutSeconds) {
        Process process = null;
        try {
            log.info("在目录 [{}] 中执行命令: {}", workingDir.getAbsolutePath(), command);

            // Hutool 的 RuntimeUtil.exec 会自动处理命令和参数的分割
            process = RuntimeUtil.exec(null, workingDir, command);

            // 开启新线程来处理标准输出和错误输出，防止缓冲区被填满导致进程阻塞
            // 这一步对于长时间运行的命令（如 npm install）非常重要
            StreamGobbler stdGobbler = new StreamGobbler(process.getInputStream(), log::info);
            StreamGobbler errGobbler = new StreamGobbler(process.getErrorStream(), log::error);
            new Thread(stdGobbler).start();
            new Thread(errGobbler).start();

            // 等待进程完成，并设置超时
            boolean finished = process.waitFor(timeoutSeconds, TimeUnit.SECONDS);

            if (!finished) {
                log.error("命令执行超时（{}秒），强制终止进程: {}", timeoutSeconds, command);
                process.destroyForcibly();
                return false;
            }

            int exitCode = process.exitValue();
            if (exitCode == 0) {
                log.info("命令执行成功: {}", command);
                return true;
            } else {
                log.error("命令执行失败，退出码: {}。命令: {}", exitCode, command);
                return false;
            }
        } catch (Exception e) {
            log.error("执行命令 [{}] 时发生异常: {}", command, e.getMessage(), e);
            // 如果进程已经启动，则销毁它
            if (process != null) {
                process.destroyForcibly();
            }
            return false;
        }
    }

    /**
     * 内部类，用于异步消费进程的输出流，避免缓冲区满导致阻塞。
     */
    private static class StreamGobbler implements Runnable {
        private final java.io.InputStream inputStream;
        private final java.util.function.Consumer<String> consumer;

        public StreamGobbler(java.io.InputStream inputStream, java.util.function.Consumer<String> consumer) {
            this.inputStream = inputStream;
            this.consumer = consumer;
        }

        @Override
        public void run() {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {
                reader.lines().forEach(consumer);
            } catch (java.io.IOException e) {
                // 流关闭时可能会抛出异常，可以忽略或记录
                log.warn("读取进程输出流时发生IO异常: {}", e.getMessage());
            }
        }
    }
}